#ifndef __M_SERVER_H__
#define __M_SERVER_H__
#include"db.hpp"
#include"matcher.hpp"
#include"online.hpp"
#include"room.hpp"
#include"util.hpp"
#include"session.hpp"

#define WWWROOT "./wwwroot"
class gobang_server{
    private:
        //声明顺序就是初始化顺序，所以声明顺序要保证
        //服务器启动，创建所有对象
        std::string _web_root;
        wsserver_t _wssrt;
        user_table _ut;
        online_manager _om;
        room_manager _rm;
        matcher<uint64_t> _mm;
        session_manager _sm;
    private:
        //处理静态请求：把网页数据发给客户端
        void file_handle(wsserver_t::connection_ptr &conn)
        {
            //步骤：1.先获取请求
            websocketpp::http::parser::request req = conn->get_request();
            std::string uri = req.get_uri();
            std::string realpath = _web_root + uri;
            //2.设定要找到的文件（有可能是 / 根目录这时候默认将其转入登录页面，和一般网页一样刚进入时先登录）
            //“/”和'/'是有区别的双引号的是存进了一个const字符串数组中，单引号就是这个字符的ASCII码值。
            if(realpath.back() == '/') realpath += "login.html";
            //3.获取文件后，将其传入一个stringbody中（为了传输）
            std::string body;
            bool ret = file_util::read(realpath,body);
            //4.如果没有找到文件返回一个404页面
            if(ret == false)
            {
                //没找到组织404
                body += "<html>";
                body += "<head>";
                //在“”中不能再出现一次“”会出错
                body += "<meta charset='UTF-8'/>";
                body += "</head>";
                body += "<body>";
                body += "<h1> NOT FOUND </h1>";
                body += "</body>";
                conn->set_body(body);
                conn->set_status(websocketpp::http::status_code::not_found);
                return;
            }
            //5.如果找到则把文件发出
            conn->set_body(body);
            conn->set_status(websocketpp::http::status_code::ok);
        }
        
        void http_resp(wsserver_t::connection_ptr &conn,bool ret,websocketpp::http::status_code::value stat,const std::string &reason)
        {
            //这里用来编辑resp
            Json::Value resp_json;
            resp_json["result"] = ret;
            resp_json["reason"] = reason;
            //将resp序列化后传入conn
            std::string resp_body;
            json_util::serialize(resp_json,resp_body);
            //设置状态码和正文
            conn->set_status(stat);
            conn->set_body(resp_body);
            //设置报头，表示这里的传输内容是应用json格式
            conn->append_header("Content-Type","application/json");
            return;
        }
        //处理注册请求
        void reg_handle(wsserver_t::connection_ptr &conn)
        {
            websocketpp::http::parser::request req = conn->get_request();
            //注册：1.从链接中中获取用户数据
            std::string req_body = conn->get_request_body();
            //2.对数据反序列化
            Json::Value body;
            bool ret = json_util::unserialize(body,req_body);
            if(ret == false)
            {
                //说明此时序列化失败
                DLOG("注册反序列化失败");
                //组织正文返回给客户端
                return http_resp(conn,false,websocketpp::http::status_code::bad_request,"请求的正文格式出错反序列化失败");
            }
            //3.把数据放到数据库中
            if(body["username"].isNull() || body["password"].isNull())
            {
                //说明没输入用户名或者密码
                DLOG("没输入用户名或者密码");
                return http_resp(conn,false,websocketpp::http::status_code::bad_request,"没输入用户名或者密码"); 
            }
            ret =  _ut.insert(body);
            if(ret == false)
            {
                //说明插入数据库失败
                DLOG("用户插入数据库失败");
                return http_resp(conn,false,websocketpp::http::status_code::bad_request,"用户插入数据库失败"); 
            }
            //4.返回结果
            return http_resp(conn,true,websocketpp::http::status_code::ok,"用户注册成功"); 
        }
        //处理登录请求
        void login_handle(wsserver_t::connection_ptr &conn)
        {
            //和注册差不多：
            //1.从conn中获取正文：用户名和密码
            std::string login_req_body = conn->get_request_body();
            Json::Value login_info;
            bool ret = json_util::unserialize(login_info,login_req_body);
            if(ret == false)
            {
                //说明此时序列化失败
                DLOG("登录序列化失败");
                //组织正文返回给客户端
                return http_resp(conn,false,websocketpp::http::status_code::bad_request,"请求的正文格式出错");
            }
            //2.获取到后，先判断是否输入为空
            if(login_info["username"].isNull() || login_info["password"].isNull())
            {
                //说明没输入用户名或者密码
                DLOG("没输入用户名或者密码");
                return http_resp(conn,false,websocketpp::http::status_code::bad_request,"没输入用户名或者密码"); 
            }
            //3.不为空，那就去数据库中查找。
            ret = _ut.login(login_info);
            if(ret == false)
            {
                //说明此时登录失败
                DLOG("密码或者用户名错误");
                //组织正文返回给客户端
                return http_resp(conn,false,websocketpp::http::status_code::bad_request,"密码或者用户名错误");
            }
            //3.1为用户设立session长连接
            uint64_t uid = login_info["id"].asUInt64();
            ss_ptr spr = _sm.session_create(uid,LOGGIN);
            //智能指针get
            if(spr.get() == nullptr)
            {
                //说明session创建失败
                DLOG("%ld,%p创建会话失败",uid,spr.get());
                return http_resp(conn, false, websocketpp::http::status_code::internal_server_error , "创建会话失败");
            }
            //如果30秒后仍然没有请求，session会关闭
            _sm.set_session_expire_time(spr->get_ssid(),SESSION_TIMEOUT);
            //4.找到则返回
            //要将sessionid也返回
            std::string cookie_id = "SSID=" + std::to_string(spr->get_ssid());
            conn->append_header("Set-Cookie",cookie_id);
            return http_resp(conn, true, websocketpp::http::status_code::ok , "登录成功");
        }
        //通过cookie的正文信息找到ssid
        bool get_ssid_from_cookie(const std::string& cookie_str,const std::string& key,std::string &val)
        {
            //Cookie:SSID=?; path=?; 
            //cookie_str是Cookie的value;
            //所以每个key之间的分割是“; ”。
            //key和value的分割是“=”.
            std::string sep = "; ";
            //先整体把cs分开
            std::vector<std::string> key_values;
            str_util::spilt(cookie_str,sep,key_values);
            //取出每一个keyvalue再次分割取出SSID的value
            for(auto& key_value:key_values)
            {
                std::vector<std::string> value;
                str_util::spilt(key_value,"=",value);
                if(value.size() != 2)continue;
                else
                {
                    if(value[0] == key)
                    {
                        val = value[1];
                        return true;
                    } 
                }
            }
            return false;
        }
        //处理个人信息请求
        void info_handle(wsserver_t::connection_ptr &conn)
        {
            //1.先获取cookie，从cookie里找到SSID
            std::string cookie_str = conn->get_request_header("Cookie");
            if(cookie_str.empty())
            {
                //说明没有Cookie信息，说明这次请求是错误的，客户端要重新登录
                return http_resp(conn,false,websocketpp::http::status_code::bad_request,"没有cookie信息，请重新登录");
            }
            //1.1cookie可能找到了但是没有找到SSID
            std::string ssid_str;
            bool ret = get_ssid_from_cookie(cookie_str,"SSID",ssid_str);
            if(ret == false)
            {
                //说明没找到SSID的信息
                return http_resp(conn,false,websocketpp::http::status_code::bad_request,"没有SSID信息，请重新登录");
            }
            //2.通过SSID找到session
            ss_ptr ssp = _sm.get_session_by_sid(std::stol(ssid_str));
            if(ssp.get() == nullptr)
            {
                //说明没找到对应的对话，一律说明对话已经过期，但这个session没进行更新
                return http_resp(conn,false,websocketpp::http::status_code::bad_request,"登录过期，请重新登录");
            }
            //3.根据ssid找到用户信息
            uint64_t uid = ssp->get_user();
            Json::Value user_info;
            ret = _ut.select_user_by_id(uid,user_info);
            if(ret == false)
            {
                //说明此时用户信息没找到。可能是因为根本没有这个人
                return http_resp(conn,false,websocketpp::http::status_code::bad_request,"没有用户信息，请重新登录");
            }
            //4.组织正文返回用户信息给前端
            std::string body;
            json_util::serialize(user_info,body);
            conn->set_body(body);
            conn->append_header("Content-type","application/json");
            conn->set_status(websocketpp::http::status_code::ok);
            //5.刷新session的过期时间
            _sm.set_session_expire_time(ssp->get_ssid(),SESSION_TIMEOUT);
        }
        //当收到http请求时的回调函数
        void http_callback(websocketpp::connection_hdl hdl)
        {
            //接收http请求根据请求划分处理函数：应该有4个处理函数，1.对于静态请求的处理：需要获取对应的网址数据
            //2.对于登录请求的处理：有登录数据的发送3.对于注册请求的处理：有注册数据的发送4.对于用户信息请求的处理：有信息请求的数据发送
            wsserver_t::connection_ptr conn = _wssrt.get_con_from_hdl(hdl);//从接收的连接句柄里获取连接的指针
            //获取请求；
            websocketpp::http::parser::request req = conn->get_request();
            std::string method = req.get_method();
            std::string uri = req.get_uri();
            //根据请求类型划分不同的函数
            if(method == "POST" && uri == "/reg")
            return reg_handle(conn);
            else if(method == "POST" && uri == "/login")
            return login_handle(conn);
            else if(method == "GET" && uri == "/info")
            return info_handle(conn);
            else 
            return file_handle(conn);
        }
        //websocket的响应返回函数
        void ws_resp(wsserver_t::connection_ptr conn,Json::Value& resp)
        {
            std::string body;
            json_util::serialize(resp,body);
            conn->send(body);
        }
        //从cookie中截取session部分的函数
        ss_ptr get_session_from_cookie(wsserver_t::connection_ptr conn)
        {
            Json::Value resp_err;
            //1.先获取到cookie
            std::string cookie_str = conn->get_request_header("Cookie");
            //判断是否拿到,没拿到就组织正文返回
            if(cookie_str.empty())
            {
                resp_err["optype"] = "hall_ready";
                resp_err["reason"] = "没有找到Cookie信息，请重新登录";
                resp_err["result"] = false;
                ws_resp(conn,resp_err);
                return ss_ptr();
            }
            //2.从cookie中截取SSID：
            std::string ssid_str;
            bool ret = get_ssid_from_cookie(cookie_str,"SSID",ssid_str);
            if(ret == false)
            {
                resp_err["optype"] = "hall_ready";
                resp_err["reason"] = "没有找到SSID信息，请重新登录";
                resp_err["result"] = false;
                ws_resp(conn,resp_err);
                return ss_ptr();
            }
            //3.通过SSID返回session
            ss_ptr ssp = _sm.get_session_by_sid(std::stol(ssid_str));
            if(ssp.get() == nullptr)
            {
                resp_err["optype"] = "hall_ready";
                resp_err["reason"] = "没有找到session，请重新登录";
                resp_err["result"] = false;
                ws_resp(conn,resp_err);
                return ss_ptr();
            }
            return ssp;
        }
        //进入大厅的回调函数
        void wsopen_game_hall(wsserver_t::connection_ptr conn)
        {
            //游戏大厅长连接建立成功
            Json::Value resp_json;
            //1.判断是否有session（即登录状态）
            ss_ptr ssp = get_session_from_cookie(conn);
            if(ssp.get() == nullptr) return;
            //2.判断用户是否重复登录
            uint64_t uid = ssp->get_user();
            if(_om.is_in_hall(uid) || _om.is_in_room(uid))
            {
                resp_json["optype"] = "hall_ready";
                resp_json["result"] = false;
                resp_json["reason"] = "重复登录，进入大厅失败";
                return ws_resp(conn,resp_json);
            }
            //3.将用户加入游戏大厅管理
            _om.enter_hall(uid,conn);
            //4.给客户端返回游戏大厅建立连接成功
            resp_json["optype"] = "hall_ready";
            resp_json["result"] = true;
            ws_resp(conn,resp_json);
            //5.因为在大厅所以要把session设定为永久
            _sm.set_session_expire_time(ssp->get_ssid(),SESSION_FOREVER);
        }
        void wsopen_game_room(wsserver_t::connection_ptr conn)
        {
            //游戏大厅长连接建立成功
            Json::Value resp_json;
            //1.判断是否有session（即登录状态）
            ss_ptr ssp = get_session_from_cookie(conn);
            if(ssp.get() == nullptr)return;
            //2.判断用户是否重复登录
            uint64_t uid = ssp->get_user();
            if(_om.is_in_hall(uid) || _om.is_in_room(uid))
            {
                resp_json["optype"] = "room_ready";
                resp_json["result"] = false;
                resp_json["reason"] = "重复登录，进入房间失败";
                
                return ws_resp(conn,resp_json);
            }
            //3.将用户加入游戏room管理
            _om.enter_room(uid,conn);
            //4.判断房间是否建立成功
            room_ptr rp = _rm.get_room_by_uid(uid);
            if(rp.get() == nullptr)
            {
                resp_json["optype"] = "room_ready";
                resp_json["result"] = false;
                resp_json["reason"] = "没有找到玩家的房间信息";
                return ws_resp(conn,resp_json);
            }
            
            //5.因为在大厅所以要把session设定为永久
            _sm.set_session_expire_time(ssp->get_ssid(),SESSION_FOREVER);
            //6.给客户端返回游戏房间建立连接成功
            resp_json["optype"] = "room_ready";
            resp_json["result"] = true;
            resp_json["room_id"] = (Json::UInt64)rp->room_id();
            resp_json["uid"] = (Json::UInt64)uid;
            resp_json["white_id"] = (Json::UInt64)rp->get_white_id();
            resp_json["black_id"] = (Json::UInt64)rp->get_black_id();
            return ws_resp(conn,resp_json);
        }
        //收到连接请求的回调函数
        void wsopen_callback(websocketpp::connection_hdl hdl)
        {
            //有两种构成长连接的情况：这时需要把http协议切换为websocket协议
            
            //1.进入大厅
            //2.进入游戏
            //获取连接
            wsserver_t::connection_ptr conn = _wssrt.get_con_from_hdl(hdl);
            //获取请求
            websocketpp::http::parser::request req = conn->get_request();
            //获取请求类型
            std::string uri = req.get_uri();
            //1.进入大厅
            if(uri == "/hall")
            {
                return wsopen_game_hall(conn);
            }
            //2.进入游戏房间
            else if(uri == "/room")
            {
                return wsopen_game_room(conn);
            }
        }
        //收到退出大厅请求的回调函数
        void wsclose_game_hall(wsserver_t::connection_ptr conn)
        {
            //游戏大厅长连接关闭
            Json::Value resp_json;
            //1.判断是否有session（即登录状态）
            ss_ptr ssp = get_session_from_cookie(conn);
            if(ssp.get() == nullptr)return;
            uint64_t uid = ssp->get_user();
            //2.将用户从大厅中移除
            _om.exit_hall(uid);
            //3.将session设置过期时间
            _sm.set_session_expire_time(ssp->get_ssid(),SESSION_TIMEOUT);
        }
        //收到退出房间请求的回调函数
        void wsclose_game_room(wsserver_t::connection_ptr conn)
        {
            //游戏大厅长连接关闭
            Json::Value resp_json;
            //1.判断是否有session（即登录状态）
            ss_ptr ssp = get_session_from_cookie(conn);
            if(ssp.get() == nullptr)return;
            uint64_t uid = ssp->get_user();
            //2.将用户从大厅中移除
            _om.exit_room(uid);
            //3.将session设置过期时间
            _sm.set_session_expire_time(ssp->get_ssid(),SESSION_TIMEOUT);
            //4.把用户从房间中放回游戏大厅
            _rm.user_exit(uid);
        }
        //收到关闭请求的回调函数
        void wsclose_callback(websocketpp::connection_hdl hdl)
        {
            //获取连接
            wsserver_t::connection_ptr conn = _wssrt.get_con_from_hdl(hdl);
            //获取请求
            websocketpp::http::parser::request req = conn->get_request();
            //获取请求类型
            std::string uri = req.get_uri();
            //1.退出大厅
            if(uri == "/hall")
            {
                return wsclose_game_hall(conn);
            }
            //2.退出房间
            else if(uri == "/room")
            {
                return wsclose_game_room(conn);
            }
        }
        void msg_game_hall(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg)
        {
            Json::Value resp_json;
            std::string resp_body;
            //1.先获取session
            ss_ptr ssp = get_session_from_cookie(conn);
            if(ssp.get() == nullptr) return;
            uint64_t uid = ssp->get_user();
            //2.获取消息
            std::string req_body = msg->get_payload();//获取负载载荷
            Json::Value req_json;
            bool ret = json_util::unserialize(req_json,req_body);
            if(ret == false)
            {
                resp_json["result"] = false;
                resp_json["reason"] = "msg反序列化失败。";
                return ws_resp(conn,resp_json);
            }
            //获取到信息后，根据按钮属性进行分类解析
            if(!req_json["optype"].isNull() && req_json["optype"] == "match_start")
            {
                //往匹配队列中加人
                _mm.add(uid);
                //同时放回匹配开始的信息
                resp_json["optype"] = "match_start";
                resp_json["result"] = true;
                return ws_resp(conn,resp_json);
            }
            else if(!req_json["optype"].isNull() && req_json["optype"] == "match_stop")
            {
                //将用户从匹配队列中删除
                _mm.del(uid);
                //同时放回匹配开始的信息
                resp_json["optype"] = "match_stop";
                resp_json["result"] = true;
                return ws_resp(conn,resp_json);
            }
            //如果两个信息都不是说明是未知的信息
            resp_json["optype"] = "unknow";
            resp_json["result"] = false;
            resp_json["reason"] = "请求类型未知";
            return ws_resp(conn,resp_json);
        }
        void msg_game_room(wsserver_t::connection_ptr conn, wsserver_t::message_ptr msg)
        {
            Json::Value resp_json;
            std::string resp_body;
            //1.先获取session
            ss_ptr ssp = get_session_from_cookie(conn);
            if(ssp.get() == nullptr)
            {
                DLOG("房间-session信息没找到");
                 return;
            }
            uint64_t uid = ssp->get_user();
            //确定在哪个房间
            room_ptr rp = _rm.get_room_by_uid(uid);
            if(rp.get() == nullptr)
            {
                resp_json["optype"] = "unknow";
                resp_json["result"] = false;
                resp_json["reason"] = "msg-查找房间失败";
                return ws_resp(conn,resp_json);
            }
            //对消息进行反序列化
            std::string req_body = msg->get_payload();//获取负载载荷
            Json::Value req_json;
            bool ret = json_util::unserialize(req_json,req_body);
            if(ret == false)
            {
                resp_json["optype"] = "unknow";
                resp_json["result"] = false;
                resp_json["reason"] = "msg反序列化失败。";
                return ws_resp(conn,resp_json);
            }
            //把拿到的消息交给room_ptr解决
            DLOG("收到房间请求，正在处理请求");
            //让房间模块对匹配成功时的请求进行处理
            return rp->handle_request(req_json);
        }
        //从连接中收到信息时的回调函数
        //类似聊天，按钮传递等等信息
        void msg_callback(websocketpp::connection_hdl hdl, wsserver_t::message_ptr msg)
        {
            //获取连接
            wsserver_t::connection_ptr conn = _wssrt.get_con_from_hdl(hdl);
            //获取请求
            websocketpp::http::parser::request req = conn->get_request();
            //获取请求类型
            std::string uri = req.get_uri();
            //1.退出大厅
            if(uri == "/hall")
            {
                return msg_game_hall(conn,msg);
            }
            //2.退出房间
            else if(uri == "/room")
            {
                return msg_game_room(conn,msg);
            }
        }
    public:
        gobang_server(const std::string &password, const std::string &dbname, const std::string &host, const std::string &user, const uint16_t port = 3306
        ,const std::string &wwwroot = WWWROOT)
        :_web_root(wwwroot),_ut(password,dbname,host,user,port),_om(),_rm(&_om,&_ut),_mm(&_rm,&_ut,&_om),_sm(&_wssrt)
        {
            //不要websocket的log
            _wssrt.set_access_channels(websocketpp::log::alevel::none);
            //异步io我不关心你过程怎么来的，你只要把结果给我就好了
            _wssrt.init_asio();
            _wssrt.set_reuse_addr(true);
            //bind就是将函数和参数绑定，因为如果我们直接传入函数的话，我们就没办法指定参数，如果用band，可以使用占位符的方法，表示是第几个参数，
            //_wssrt.set_http_handler(gobang_server::http_callback,this);错误写法，因为set只能有一个参数，如果我们要给httpback传参就必须用band
            _wssrt.set_http_handler(std::bind(&gobang_server::http_callback,this,std::placeholders::_1));
            _wssrt.set_open_handler(std::bind(&gobang_server::wsopen_callback,this,std::placeholders::_1));
            _wssrt.set_close_handler(std::bind(&gobang_server::wsclose_callback,this,std::placeholders::_1));
            _wssrt.set_message_handler(std::bind(&gobang_server::msg_callback,this,std::placeholders::_1,std::placeholders::_2));
        }
        void start(int port)
        {
            //_wssrt开始监听port端口的连接
            _wssrt.listen(port);
            //_listen到后接收port端口的数据
            _wssrt.start_accept();
            //执行
            _wssrt.run();
        }
};

#endif
